/*
 Copyright (c) 2013 MeherTJ G. All rights reserved.
 License: LGPL for personnal study or free software.
 */
#pragma once
#include "zen_audio.h"
#include "zen_file.h"
#include "zen_buffer.h"
#include "zen_exception.h"
#include "zen_endian.h"
#include <iostream>

namespace Zen
{
	class AudioWav : public AudioDecoder, public AudioEncoder
	{
	public:
		// throw (Zen::Exception)
		std::shared_ptr<Audio> decode(const std::vector<uint8_t>& value) override;

		// throw (Zen::Exception)
		std::vector<uint8_t> encode(const Zen::Audio&) override;
	};
} // namespace Zen

////////////////////////
////////////////////////

namespace Zen
{
	struct AudioWavHead
	{
		Byte4 fiRiffID{}; // "RIFF"
		uint32_t fiRiffSize{};
		Byte4 fiAudioType{}; // "WAVE"
		Byte4 fiFmtID{}; // "fmt "
		uint32_t fiFmtSize{};
		uint16_t fiFormatTag{};
		uint16_t fiChannels{};
		uint32_t fiSPS{};
		uint32_t fiBPS{};
		uint16_t fiBlockAlign{};
		uint16_t fiBPSample{};

		void Set(size_t channel, size_t sample_size, size_t frequency, size_t sample_count, size_t data_size)
		{
			this->fiRiffID.Set('R', 'I', 'F', 'F');
			this->fiRiffSize = (uint32_t)data_size + (44 - 8);
			this->fiAudioType.Set('W', 'A', 'V', 'E');
			this->fiFmtID.Set('f', 'm', 't', ' ');
			this->fiFmtSize = 16;
			this->fiFormatTag = 1;
			this->fiChannels = (uint16_t)channel;
			this->fiBlockAlign = (uint16_t)sample_size;
			this->fiBPSample = (uint16_t)sample_size * 8;
			this->fiSPS = (uint32_t)frequency;
			this->fiBPS = (uint32_t)(frequency * sample_size);
#if ZEN_BIG_ENDIAN
			this->fiRiffSize = ::EndianSwap32(this->fiRiffSize);
			this->fiFmtSize = ::EndianSwap32(this->fiFmtSize);
			this->fiFormatTag = ::EndianSwap16(this->fiFormatTag);
			this->fiChannels = ::EndianSwap16(this->fiChannels);
			this->fiSPS = ::EndianSwap32(this->fiSPS);
			this->fiBPS = ::EndianSwap32(this->fiBPS);
			this->fiBlockAlign = ::EndianSwap16(this->fiBlockAlign);
			this->fiBPSample = ::EndianSwap16(this->fiBPSample);
#endif
		}
	};

	inline std::shared_ptr<Audio> AudioWav::decode(std::vector<uint8_t> const& value)
	{
		Zen::BufferReader reader(value);

		AudioWavHead head;

		__zen_must_else(reader.Read(head), "invalid wav value");

#if ZEN_BIG_ENDIAN
		head.fiRiffSize = ::EndianSwap32(head.fiRiffSize);
		head.fiFmtSize = ::EndianSwap32(head.fiFmtSize);
		head.fiFormatTag = ::EndianSwap16(head.fiFormatTag);
		head.fiChannels = ::EndianSwap16(head.fiChannels);
		head.fiSPS = ::EndianSwap32(head.fiSPS);
		head.fiBPS = ::EndianSwap32(head.fiBPS);
		head.fiBlockAlign = ::EndianSwap32(head.fiBPSample);
#endif

		uint16_t fiCbSize = 0;

		if (head.fiFmtSize == 18)
		{
			__zen_must_else(reader.Read(fiCbSize), "error wav format");
#if ZEN_BIG_ENDIAN
			fiCbSize = ::EndianSwap16(fiCbSize);
#endif
			reader.MoveGetter(fiCbSize);
		}
		else
		{
			__zen_must_else(head.fiFmtSize == 16, "invalid wave format size");
		}

		__zen_must_else(head.fiFormatTag == 1, "not PCM wav format");

		__zen_must_else(head.fiRiffID == Byte4('R', 'I', 'F', 'F') && head.fiAudioType == Byte4('W', 'A', 'V', 'E') && head.fiFmtID == Byte4('f', 'm', 't', '\x20'), "invalid wav format");

		Byte4 fiDataID;
		uint32_t fiDataSize = 0;

		__zen_must_else(reader.Read(fiDataID) && reader.Read(fiDataSize), "invalid wav value info");
#if ZEN_BIG_ENDIAN
		fiDataSize = ::EndianSwap32(fiDataSize);
#endif
		if (!(fiDataID == Byte4('d', 'a', 't', 'a')))
		{
			__zen_must_else(reader.GetGetter() + fiDataSize <= value.size(), "invalid fact value");
			reader.MoveGetter(fiDataSize);

			__zen_must_else(reader.Read(fiDataID) && reader.Read(fiDataSize), "invalid wav value info");

#if ZEN_BIG_ENDIAN
			fiDataSize = ::EndianSwap32(fiDataSize);
#endif
		}

		__zen_must_else(fiDataID == Byte4('d', 'a', 't', 'a'), "invalid value format");

		uint32_t length = (uint32_t)(fiDataSize / head.fiBlockAlign);
		uint32_t size = length * (uint32_t)head.fiBlockAlign;

		__zen_must_else(reader.GetGetter() + size <= value.size(), "invalid value size");

		auto audio = Audio::Generate(head.fiChannels, head.fiBlockAlign, head.fiSPS, length);

		__zen_must(audio->size() == size);

		reader.Read(audio->data(), audio->size());

		return audio;
	}

	inline std::vector<uint8_t> AudioWav::encode(Audio const& audio)
	{
		Byte4 fiDataID('d', 'a', 't', 'a');
		Byte4 fiDataSize((uint32_t)audio.size());

		AudioWavHead head;
		head.Set(audio.channel(), audio.sampleSize(), audio.frequency(), audio.sampleCount(), audio.size());

		std::vector<uint8_t> value;
		value.reserve(sizeof(head) + audio.size() + 8);
		auto head_buf = reinterpret_cast<char const*>(&head);
		value.assign(head_buf, head_buf + sizeof(head));
		value.insert(value.end(), fiDataID.bytes, fiDataID.bytes + 4);
		value.insert(value.end(), fiDataSize.bytes, fiDataSize.bytes + 4);
		value.insert(value.end(), audio.data(), audio.data() + audio.size());

		return value;
	}
} // namespace Zen
